#include <iostream>

/**
 * Важно!
 * Не запускайте эту функцию!
 */
void new_expression_syntax_example() {

    new (std::nothrow) int;

    new (std::nothrow) int(5);

    new (std::nothrow) int{5};
}

void new_expression_example() {

    int *i_ptr = new (std::nothrow) int(5);
    std::cout << *i_ptr << std::endl;
    *i_ptr += 2;
    std::cout << *i_ptr << std::endl;
    

    delete i_ptr;
}

void new_expression_reference() {
    int *i_ptr = new (std::nothrow) int(5);
    
    int &i_ref = *i_ptr;
    std::cout << *i_ptr << " " << i_ref << std::endl;
    std::cout << i_ptr << " " << &i_ref << std::endl;

    delete i_ptr;
}

void delete_expression_example() {
   int *i_ptr = new (std::nothrow) int(5); 

   std::cout << i_ptr << std::endl;

   delete i_ptr;

   std::cout << i_ptr << std::endl;
}

void new_expression_failure() {
    {
        int *i_ptr = new (std::nothrow) int(5);
        if (nullptr == i_ptr) {
            std::cout << "Memory allocation error" << std::endl;
        } else {
            std::cout << *i_ptr << std::endl;
            *i_ptr += 2;
            std::cout << *i_ptr << std::endl;
            delete i_ptr;
        }
    }
    {
        try {
            int *i_ptr = new int(5);
            std::cout << *i_ptr << std::endl;
            *i_ptr += 2;
            std::cout << *i_ptr << std::endl;
            delete i_ptr;
        } catch(std::bad_alloc const &e) {
            std::cout << "Memory allocation error" << std::endl;
        }
    }
}

/**
 * Важно!
 * Не запускайте эту функцию!
 */
void use_uninitialized() {
    int *uninit_ptr; // неинициализированный указатель
    std::cout << *uninit_ptr << std::endl;

    int *null_ptr = nullptr; // нулевой указатель
    std::cout << *null_ptr << std::endl;

    // непроверенный указатель
    int *unchecked_ptr = new (std::nothrow) int(5);
    std::cout << *unchecked_ptr << std::endl;
}

/**
 * Важно!
 * Не вызывайте эту функцию!
 */
void use_after_free() {
    {
        int *dangling_pointer = new (std::nothrow) int(5);
        if (nullptr != dangling_pointer) {
            std::cout << *dangling_pointer << std::endl;
            delete dangling_pointer;
            *dangling_pointer += 2;
        }
    }

    {
        int *ptr = new (std::nothrow) int(5);
        if (nullptr != ptr) {
            int &dangling_ref = *ptr;
            delete ptr;
            dangling_ref += 2;
        }
    }
}

void wrog_free() {
    {
        int i;
        int *i_ptr = &i;
        delete i_ptr;
    }

    {
        int *ptr = new (std::nothrow) int(5);
        unsigned char *uch_ptr = reinterpret_cast<unsigned char *>(ptr);
        delete (uch_ptr + 1);
    }
}

void double_free() {
    int *ptrA = new (std::nothrow) int(5);
    int *ptrB = ptrA;
    if (nullptr != ptrA) {
        // Some code goes here ...
        delete ptrA;
        delete ptrB;
    } 
}

void memory_leak() {
    while (true) {
        int *tmp = new (std::nothrow) int(5);
    }
}

void new_array_expression() {
    int *one_element = new (std::nothrow) int[1] {5};
    
    int *some_elements = new (std::nothrow) int[3] {1, 2, 3};

    int *some_zero_elemetns = new (std::nothrow) int[4] {0};

    int *some_uninitialized_elements = new (std::nothrow) int [5];

    int *zero_elements = new (std::nothrow) int[0];
}


void delete_array_expression() {
    int *one_element = new (std::nothrow) int[1];
    delete[] one_element;

    int *some_elements = new (std::nothrow) int[3] {1, 2, 3};
    delete[] some_elements;

    int *zero_elements = new (std::nothrow) int[0];
    delete[] zero_elements;
}

void pointer_arithmetic_example() {
    int *some_elements = new (std::nothrow) int[3] {1, 2, 3};
    std::cout << *(some_elements + 0) << '\n'; // первый элемент
    std::cout << *(some_elements + 1) << '\n'; // второй элемент
    std::cout << *(some_elements + 2) << '\n'; // третий элемент

    delete[] some_elements;
}

void pointer_arithmetic_to_indexing() {
    int *some_elements = new (std::nothrow) int[3] {1, 2, 3};

    int a = some_elements[0];
    int b = 2[some_elements];

    int *central_element_address = some_elements + 1;
    std::cout << a << " " << (-1)[central_element_address] << '\n';
    std::cout << b << " " << 1[central_element_address] << '\n';

    std::cout << std::endl;

    delete[] some_elements;
}

void pointers_indexing_more_examples() {
    {
        int *elements = new int[2] {-2, 2};
        int *p = elements;
        std::cout << ++*p << std::endl;
        delete[] elements;
    }
    {
        int *elements = new int[2] {-2, 2};
        int *p = elements;
        std::cout << *++p << std::endl;
        delete[] elements;
    }
    {
        int *elements = new int[2] {-2, 2};
        int *p = elements;
        std::cout << ++p[0] << " " << p[0] << std::endl;
        delete[] elements;
    }
    {
        int *elements = new int[2] {-2, 2};
        int *p = elements;
        std::cout << p++[0] << " " << p[0] << std::endl;
        delete[] elements;
    }
}

int main() {
    if (false) new_expression_example();
    if (false) new_expression_reference();
    if (false) delete_expression_example();
    if (false) new_expression_failure();
    if (false) pointer_arithmetic_example();
    if (false) pointer_arithmetic_to_indexing();
    if (true) pointers_indexing_more_examples();
    return 0;
}
